pub const plic = @import("driver/plic.zig");
pub const syscon = @import("driver/syscon.zig");
pub const uart = @import("driver/uart.zig");
pub const console = @import("driver/console.zig");
pub const @"null" = @import("driver/null.zig");
pub const virtio = @import("driver/virtio.zig");
pub const pci = @import("driver/pci.zig");
pub const e1000 = @import("driver/e1000.zig");
pub const rtc = @import("driver/rtc.zig");

const std = @import("std");
const log = std.log.scoped(.driver);
const os = @import("os.zig");
const gpr = @import("riscv").gpr;

const Init = os.proc.Proc.Kthread.Fn;

/// Driver initialization call structure stored in dedicated initcall section.
///
/// Most initcalls are invoked during system initialization, but some (e.g., UART)
/// may be called earlier during kernel boot. Manual invocation sets func to null
/// to prevent duplicate initialization during the main init phase.
const InitCall = extern struct {
    /// Initialization function for the driver.
    func: ?*const anyopaque,
    /// Unique driver identifier.
    /// Matches FDT 'compatible' property for physical devices;
    /// A distinct logical name for virtual devices (e.g., "/dev/console").
    name: [*:0]const u8,
};

extern var _initcalls_start: InitCall;
extern const _initcalls_end: InitCall;
const initcalls: [*]InitCall = @ptrCast(&_initcalls_start);

/// Initializes all drivers by executing their init functions in parallel threads.
/// Scans the `.initcalls` ELF section containing driver initialization records,
/// spawns a kernel thread for each entry, and waits for all threads to complete.
pub fn init(_: ?*const anyopaque) void {
    // Create a kernel thread for each init call
    const p = os.proc.current().?;
    const len = &_initcalls_end - &_initcalls_start;
    for (initcalls[0..len]) |*driver| {
        // Skip if initialization function was already called
        if (driver.func == null) continue;
        const func: Init = @ptrCast(@alignCast(driver.func.?));

        var nodes = os.fdt.get(std.mem.span(driver.name)) orelse {
            // Spawn virtual device driver
            _ = p.spawn(std.mem.span(driver.name), .{ .func = func });
            continue;
        };

        // Multiple devices may be compatible with a single driver
        while (nodes.pop()) |node|
            _ = p.spawn(std.mem.span(driver.name), .{ .func = func, .arg = node });
    }

    // Wait for all remaining child threads to complete
    p.waitAll();

    // Releases all memory allocated by fdt since it's no longer needed.
    os.fdt.deinit();
}

/// Finds the first driver matching any compatible name in the initcall list.
pub fn find(compatibles: [][]const u8) ?*InitCall {
    const len = &_initcalls_end - &_initcalls_start;
    for (compatibles) |name| {
        for (initcalls[0..len]) |*driver| {
            if (std.mem.eql(u8, std.mem.span(driver.name), name)) return driver;
        }
    }
    return null;
}

/// Initializes a device by calling its driver and marking it as completed.
pub fn call(node: *const os.fdt.Node) !void {
    const compatibles = node.prop(.Compatible).?;
    const driver = find(compatibles) orelse return error.DeviceNotFound;
    const func: Init = @ptrCast(@alignCast(driver.func.?));
    func(node);
    driver.func = null;
}

/// Registers a driver initialization function to be called during system startup.
pub fn register(comptime func: Init, comptime unique: [:0]const u8) void {
    comptime {
        const initcall: InitCall = .{ .func = func, .name = unique };
        @export(&initcall, .{ .name = ".$." ++ unique, .section = ".initcalls." ++ unique });
    }
}

// Compile-time block ensures all declarations in this scope are analyzed
comptime {
    os.lib.refAllDeclsRecursive(@This());
}
